#include "scheme.h"
#include "time_utils.h"

Scheme::Scheme(SecretKey &secretKey, Context &context) : context(context)
{
    TimeUtils timeUtils;
    ex = new uint64_t[context.L << context.logN];
    axKey = new uint64_t[(context.L + context.K) << context.logN];
    bxKey = new uint64_t[(context.L + context.K) << context.logN];
    timeUtils.Start("CreatePk");
    AddEncKey(secretKey);
    timeUtils.Stop("CreatePk");

    timeUtils.Start("CreateRelinKey");
    AddMultKey(secretKey);
    timeUtils.Stop("CreateRelinKey");

    vx = new uint64_t[context.L << context.logN];
    polyPQPoolAx = new uint64_t[(context.L + context.K) << context.logN];
    polyPQPoolBx = new uint64_t[(context.L + context.K) << context.logN];
    polyTPools2Ax = new uint64_t *[context.blockLen];
    polyTPools2Bx = new uint64_t *[context.blockLen];
    for (int i = 0; i < context.blockLen; i++) {
        polyTPools2Ax[i] = new uint64_t[context.T << context.logN];
        polyTPools2Bx[i] = new uint64_t[context.T << context.logN];
    }

    // Scheme::LeftRotateFast && Scheme::Conjugate
    ntt_index = new uint64_t[context.N]();
    ax = new uint64_t[context.L << context.logN]();
    axax = new uint64_t[context.N * context.L];
    bxbx = new uint64_t[context.N * context.L];
    axmult = new uint64_t[context.L << context.logN]();
    bxmult = new uint64_t[context.L << context.logN]();

    // Scheme::AddLeftRotKey
    sx = new uint64_t[(context.L + context.K) * context.N]();

    // Scheme::Encode
    m = new uint64_t[context.L << context.logN]();

    // Scheme::Decode msg
    res = new complex<double>[context.N / 2]();

    // Scheme::MultAndEqual
    axaxi = new uint64_t[context.L << context.logN]();
    axbx1 = new uint64_t[context.L << context.logN]();
    axbx2 = new uint64_t[context.L << context.logN]();

    // Scheme::Square
    axbx = new uint64_t[context.L << context.logN]();


    // Scheme::MultByConst
    cnstvec = new complex<double>[context.N / 2]; // slots

    // Scheme::MultByConstVec
    mx_1 = new uint64_t[context.L << context.logN]();

    // Scheme::ConjugateAndEqual
    bxmult = new uint64_t[context.N * context.L];
    bx_CAE = new uint64_t[context.N * context.L];
    ax_CAE = new uint64_t[context.N * (context.L + context.K)];
}

/*
 * @brief ：生成加密密钥
 *
 * @param secretKey ：输入的私钥
 */
void Scheme::AddEncKey(SecretKey &secretKey)
{
    uint64_t *axEncKey = new uint64_t[context.L << context.logN]();
    uint64_t *bxEncKey = new uint64_t[context.L << context.logN]();

    context.SampleUniform(axEncKey, context.L);
    context.SampleGauss(ex, context.L);
    context.NTTAndEqual(ex, context.L);
    context.Mul(bxEncKey, axEncKey, secretKey.sx, context.L);
    context.Sub2AndEqual(ex, bxEncKey, context.L);
    encKey = Key(axEncKey, bxEncKey);
}
/*
 * @brief ：执行标量乘法，对输入多项式系数与模数向量 pVec 中的标量逐层相乘，并存储结果。
 *
 * @param res： 输出数组，用于存储乘法操作后的结果。
 * @param a： 输入数组，包含待乘标量的多项式系数。
 */
void Scheme::MulScalarP(uint64_t *res, uint64_t *a)
{
    #pragma omp parallel for collapse(2)
    for (int i = 0; i < context.L; i++) {
        for (int n = 0; n < context.N; n++){
            uint64_t index = (i << context.logN) + n;
            res[index] = a[index];
            for (int k = 0; k < context.K; k++) {
                MulModBarrett(res[index], res[index], context.pVec[k], context.qVec[i], context.twoqVec[i],
                                context.qrVecHigh[i], context.qrVecLow[i]);
            }
        }
    }
}

void Scheme::MulScalaInt(Ciphertext &cipher, double scale)
{
    int slice;
    for (int i = 0; i < cipher.l * cipher.N; i++) {
        slice = i / context.N;
        MulModBarrett(cipher.ax[i], cipher.ax[i], scale, context.qVec[slice], context.twoqVec[slice],
                      context.qrVecHigh[slice], context.qrVecLow[slice]);
        MulModBarrett(cipher.bx[i], cipher.bx[i], scale, context.qVec[slice], context.twoqVec[slice],
                      context.qrVecHigh[slice], context.qrVecLow[slice]);
    }
}

/*
 * @brief ：生成乘法密钥
 *
 * @param secretKey ：输入的私钥
 */
void Scheme::AddMultKey(SecretKey &secretKey)
{
    vector<vector<vector<uint64_t>>> rax;
    vector<vector<vector<uint64_t>>> rbx;
    context.Mul(context.sxSx, secretKey.sx, secretKey.sx, context.L);
    MulScalarP(context.polyQPool, context.sxSx);
    for (int i = 0; i < context.dNum; i++) {
        context.SampleGauss(context.ex, context.L, context.K);
        context.NTTAndEqual(context.ex, context.L, context.K);
        context.SampleUniform(axKey, context.L, context.K);
        for (int j = 0; j < context.K; j++) {
            int index = i * context.K + j;
            if (index >= context.L) {
                break;
            }
            #pragma omp parallel for
            for (int w = 0; w < context.N; w++) {
                uint64_t tmp = context.polyQPool[(index << context.logN) + w] + context.ex[(index << context.logN) + w];
                if (tmp >= context.qVec[index]) {
                    context.ex[(index << context.logN) + w] = tmp - context.qVec[index];
                } else {
                    context.ex[(index << context.logN) + w] = tmp;
                }
            }
        }
        #pragma omp parallel for collapse(2)
        for (int l = 0; l < (context.L + context.K); l++) {
            for (int n = 0; n < context.N; n++) {
                uint64_t tmp;
                uint64_t index = (l << context.logN) + n;
                MulModBarrett(tmp, axKey[index], secretKey.sx[index], context.QP[l], context.twoqpVec[l],
                            context.qprVecHigh[l], context.qprVecLow[l]);
                if (context.ex[index] < tmp) {
                    bxKey[index] = context.ex[index] - tmp + context.QP[l];
                } else {
                    ModBarrett(bxKey[index], context.ex[index] - tmp, context.QP[l],
                            context.qprVecHigh[l], context.qprVecLow[l]);
                }
            }
        }
        context.INTTAndEqual(axKey, context.L, context.K);
        context.INTTAndEqual(bxKey, context.L, context.K);
        rotate(axKey, axKey + context.L * context.N, axKey + (context.L + context.K) * context.N);
        rotate(bxKey, bxKey + context.L * context.N, bxKey + (context.L + context.K) * context.N);
        context.resultax = context.BaseConvertPQ2T(axKey);
        context.resultbx = context.BaseConvertPQ2T(bxKey);

        rax.push_back(context.resultax);
        rbx.push_back(context.resultbx);
    }
    multKey = SwitchKey(rax, rbx);
}

/*
 * @brief ：生成共轭密钥
 *
 * @param secretKey ：输入的私钥
 */
void Scheme::AddConjKey(SecretKey &secretKey)
{
    vector<vector<vector<uint64_t>>> rax;
    vector<vector<vector<uint64_t>>> rbx;
    vector<vector<uint64_t>> resultax;
    vector<vector<uint64_t>> resultbx;
    context.Conjugate(context.sxConj, secretKey.sx, context.L);
    MulScalarP(context.polyQPool, context.sxConj);
    for (int i = 0; i < context.dNum; i++) {
        context.SampleGauss(context.ex, context.L, context.K);
        context.NTTAndEqual(context.ex, context.L, context.K);
        context.SampleUniform(axKey, context.L, context.K);
        for (int j = 0; j < context.K; j++) {
            uint64_t index = i * context.K + j;
            if (index >= context.L) {
                break;
            }
            #pragma omp parallel for
            for (int w = 0; w < context.N; w++) {
                uint64_t tmp = context.polyQPool[(index << context.logN) + w] + context.ex[(index << context.logN) + w];
                if (tmp >= context.qVec[index]) {
                    context.ex[(index << context.logN) + w] = tmp - context.qVec[index];
                } else {
                    context.ex[(index << context.logN) + w] = tmp;
                }
            }
        }
        #pragma omp parallel for collapse(2)
        for (int l = 0; l < (context.L + context.K); l++) {
            for (int n = 0; n < context.N; n++) {
                uint64_t tmp;
                uint64_t index = (l << context.logN) + n;
                MulModBarrett(tmp, axKey[index], secretKey.sx[index], context.QP[l], context.twoqpVec[l],
                            context.qprVecHigh[l], context.qprVecLow[l]);
                if (context.ex[index] < tmp) {
                    bxKey[index] = context.ex[index] - tmp + context.QP[l];
                } else {
                    ModBarrett(bxKey[index], context.ex[index] - tmp, context.QP[l],
                            context.qprVecHigh[l], context.qprVecLow[l]);
                }
            }
        }
        context.INTTAndEqual(axKey, context.L, context.K);
        context.INTTAndEqual(bxKey, context.L, context.K);
        rotate(axKey, axKey + context.L * context.N, axKey + (context.L + context.K) * context.N);
        rotate(bxKey, bxKey + context.L * context.N, bxKey + (context.L + context.K) * context.N);
        resultax = context.BaseConvertPQ2T(axKey);
        resultbx = context.BaseConvertPQ2T(bxKey);
        rax.push_back(resultax);
        rbx.push_back(resultbx);
    }
    conjKey = SwitchKey(rax, rbx);
}

/*
 * @brief ：生成左旋密钥
 *
 * @param secretKey ：输入的私钥
 * @param rot ：旋转索引
 */
void Scheme::AddLeftRotKey(SecretKey &secretKey, long rot)
{
    vector<vector<vector<uint64_t>>> rax;
    vector<vector<vector<uint64_t>>> rbx;
    vector<vector<uint64_t>> resultax;
    vector<vector<uint64_t>> resultbx;
    int e = (-rot) & (context.M - 1);
    uint64_t galElInv = context.FastPow(5, e, context.M);

    context.PermuteNTTIndex(galElInv, ntt_index);
    context.PermuteNTTWithIndex(sx, secretKey.sx, ntt_index, context.L + context.K);

    for (int i = 0; i < context.L * context.N; i++) {
        uint64_t slice = i / context.N;
        for (int k = 0; k < context.K; k++) {
            if (k == 0) {
                MulModBarrett(context.polyQPool[i], secretKey.sx[i], context.pVec[k], context.qVec[slice],
                              context.twoqVec[slice], context.qrVecHigh[slice], context.qrVecLow[slice]);
            } else {
                MulModBarrett(context.polyQPool[i], context.polyQPool[i], context.pVec[k], context.qVec[slice],
                              context.twoqVec[slice], context.qrVecHigh[slice], context.qrVecLow[slice]);
            }
        }
    }

    for (int i = 0; i < context.dNum; i++) {
        context.SampleGauss(context.ex, context.L, context.K);
        context.NTTAndEqual(context.ex, context.L, context.K);
        context.SampleUniform(axKey, context.L, context.K);
        for (int j = 0; j < context.K; j++) {
            uint64_t index = i * context.K + j;
            if (index >= context.L) {
                break;
            }
            // #pragma omp parallel for
            for (int w = 0; w < context.N; w++) {
                uint64_t tmp = context.polyQPool[(index << context.logN) + w] + context.ex[(index << context.logN) + w];
                if (tmp >= context.qVec[index]) {
                    context.ex[(index << context.logN) + w] = tmp - context.qVec[index];
                } else {
                    context.ex[(index << context.logN) + w] = tmp;
                }
            }
        }
        for (int l = 0; l < context.L * context.N; l++) {
            uint64_t slice = l / context.N;
            uint64_t tmp;
            MulModBarrett(tmp, axKey[l], sx[l], context.qVec[slice], context.twoqVec[slice],
                          context.qrVecHigh[slice], context.qrVecLow[slice]);
            if (context.ex[l] < tmp) {
                bxKey[l] = context.ex[l] - tmp + context.qVec[slice];
            } else {
                ModBarrett(bxKey[l], context.ex[l] - tmp, context.qVec[slice],
                           context.qrVecHigh[slice], context.qrVecLow[slice]);
            }
        }

        for (int k = (context.L * context.N); k < (context.L + context.K) * context.N; k++) {
            uint64_t slice = k / context.N - context.L;
            uint64_t tmp;
            MulModBarrett(tmp, axKey[k], sx[k], context.pVec[slice], context.twopVec[slice],
                          context.prVecHigh[slice], context.prVecLow[slice]);
            if (context.ex[k] < tmp) {
                bxKey[k] = context.ex[k] + context.pVec[slice] - tmp;
            } else {
                ModBarrett(bxKey[k], context.ex[k] - tmp, context.pVec[slice],
                           context.prVecHigh[slice], context.prVecLow[slice]);
            }
        }
        context.INTTAndEqual(axKey, context.L, context.K);
        context.INTTAndEqual(bxKey, context.L, context.K);
        rotate(axKey, axKey + context.L * context.N, axKey + (context.L + context.K) * context.N);
        rotate(bxKey, bxKey + context.L * context.N, bxKey + (context.L + context.K) * context.N);
        resultax = context.BaseConvertPQ2T(axKey);
        resultbx = context.BaseConvertPQ2T(bxKey);
        rax.push_back(resultax);
        rbx.push_back(resultbx);
    }

    leftRotKeyMap.insert(pair<long, SwitchKey>(rot, SwitchKey(rax, rbx)));
    rotateKey = SwitchKey(rax, rbx);
}

/*
 * @brief ：生成并添加所有索引为 2 的幂次的左移（旋转）密钥到映射表中。
 *
 * @param secretKey ：输入的密钥
 */
void Scheme::AddLeftRotKeys(SecretKey &secretKey)
{
    for (long i = 0; i < context.logNh; i++) {
        long idx = 1 << i;
        if (leftRotKeyMap.find(idx) == leftRotKeyMap.end()) {
            AddLeftRotKey(secretKey, idx);
        }
    }
}

/*
 * @brief ：生成并添加所有索引为 2 的幂次的右移（旋转）密钥到映射表中。
 *
 * @param secretKey ：输入的密钥
 */
void Scheme::AddRightRotKeys(SecretKey &secretKey)
{
    for (long i = 0; i < context.logNh; i++) {
        long idx = context.Nh - (1 << i);
        if (leftRotKeyMap.find(idx) == leftRotKeyMap.end()) {
            AddLeftRotKey(secretKey, idx);
        }
    }
}

/*
 * @brief ：对输入向量进行编码，将浮点数向量转换为明文格式
 *
 * @param v： 输入向量，包含待编码的浮点数数据。
 * @param slots： 编码的槽位数量，每个槽位对应一个数据元素。
 * @param l： 编码所需的模数层级数量。
 *
 * @return Plaintext： 返回编码后的明文对象，包含编码数据及相关元数据。
 */
Plaintext Scheme::Encode(double *vals, long slots, long l)
{
    context.Encode(m, vals, slots, l);
    return Plaintext(m, context.N, slots, l);
}

/*
 * @brief ：对输入的复数向量进行编码，将复数向量转换为明文格式
 *
 * @param v： 输入向量，包含待编码的复数数据。
 * @param slots： 编码的槽位数量，每个槽位对应一个数据元素。
 * @param l： 编码所需的模数层级数量。
 *
 * @return Plaintext： 返回编码后的明文对象，包含编码数据及相关元数据。
 */
Plaintext Scheme::Encode(complex<double> *v, long slots, long l)
{
    context.Encode(m, v, slots, l);
    return Plaintext(m, context.N, slots, l);
}

/*
 * @brief ：对单个复数值进行编码。
 *
 * @param val： 输入复数值，包含待编码的数据。
 * @param l： 编码所需的模数层级数量。
 * @return Plaintext ：返回编码后的明文对象，包含编码数据及相关元数据。
 */
Plaintext Scheme::EncodeSingle(complex<double> val, long l)
{
    context.EncodeSingle(m, val, l);
    return Plaintext(m, context.N, 1, l);
}

/*
 * @brief ：解码明文对象，将其转换回复数数组。
 *
 * @param msg： 输入的明文对象，包含编码后的多项式数据及元数据。
 * @param scale： 解码时使用的缩放因子。
 *
 * @return complex<double>*： 返回解码后的复数数组。
 */
complex<double> *Scheme::Decode(Plaintext &msg, __uint128_t scale)
{
    complex<double> *res = new complex<double>[msg.slots]();
    context.Decode(msg.mx, res, msg.slots, msg.l, scale);
    return res;
}

/*
 * @brief 解码明文对象中的单个复数值。
 *
 * @param msg： 输入的明文对象，包含编码后的单个复数数据及元数据。
 *
 * @return complex<double>： 返回解码后的单个复数值。
 */
complex<double> Scheme::DecodeSingle(Plaintext &msg)
{
    complex<double> res;
    context.DecodeSingle(msg.mx, res);
    return res;
}

/*
 * @brief 使用给定的私钥对明文进行加密，生成密文。
 *
 * @param secretkey： 输入的私钥，用于加密操作。
 * @param message： 输入的明文对象，包含待加密的多项式数据及元数据。
 *
 * @return Ciphertext： 返回加密后的密文对象，包含加密后的多项式数据及元数据。
 *
 * 该函数将输入的明文 `message` 使用私钥 `secretkey` 进行加密，
 * 生成同态加密系统中的密文。加密过程中引入了随机噪声，确保加密的安全性。
 *
 * 实现步骤：
 * 1. 动态分配内存，初始化密文的两个主要部分 `ax` 和 `bx` 及噪声项 `ex`。
 * 2. 生成均匀分布的多项式 `ax`，并与私钥多项式 `sx` 相乘，结果存储在 `bx`。
 * 3. 对 `bx` 取负，并在数论变换 (NTT) 域中进行处理。
 * 4. 生成服从高斯分布的噪声多项式 `ex` 并将其转换到 NTT 域。
 * 5. 将噪声多项式 `ex` 和明文多项式 `mx` 加入到 `bx`，形成加密结果。
 * 6. 返回包含 `ax` 和 `bx` 的密文对象。
 */
Ciphertext Scheme::EncryptMsg(SecretKey &secretkey, Plaintext &message)
{
    uint64_t* axEncKey = new uint64_t[message.l << context.logN]();
    uint64_t* bxEncKey = new uint64_t[message.l << context.logN]();
    uint64_t* ex = new uint64_t[message.l << context.logN]();

    context.SampleUniform(axEncKey, message.l);
    context.Mul(axEncKey, secretkey.sx, bxEncKey, message.l);
    context.SampleGauss(ex, message.l);
    context.NTTAndEqual(ex, message.l);
    context.Sub(bxEncKey, ex, axEncKey, message.l);
    context.AddAndEqual(bxEncKey, message.mx, message.l);
    return Ciphertext(axEncKey, bxEncKey, context.N, message.slots, message.l, context.p);
}

/*
 * @brief 对明文进行加密，生成密文对象，使用公钥中的密钥进行加密操作。
 *
 * @param message： 输入的明文对象，包含待加密的数据及元数据。
 *
 * @return Ciphertext： 返回加密后的密文对象，包含密文数据及元数据。
 *
 * 该函数通过公钥（包含 `ax` 和 `bx`）对输入明文 `message` 进行加密，
 * 并生成同态加密系统中的密文对象。
 *
 * 实现步骤：
 * 1. 获取存储在 `keyMap` 中的加密密钥 `key`。
 * 2. 生成随机服从 {0, 1, -1} 分布的多项式 `vx`，并将其转换到 NTT 域。
 * 3. 使用 `vx` 和公钥多项式 `key.ax`、`key.bx` 进行乘法运算，并加入高斯噪声 `ex`。
 * 4. 将明文多项式 `message.mx` 加入到 `bx`，形成最终的密文数据。
 * 5. 返回包含 `ax` 和 `bx` 的密文对象。
 */
Ciphertext Scheme::EncryptMsg(Plaintext &message)
{

    uint64_t *axEnc = new uint64_t[message.l << context.logN]();
    uint64_t *bxEnc = new uint64_t[message.l << context.logN]();

    context.SampleZO(vx, message.l, 0);
    context.NTTAndEqual(vx, message.l);

    context.Mul(axEnc, vx, encKey.ax, message.l);

    context.SampleGauss(ex, message.l);
    context.NTTAndEqual(ex, message.l);

    context.AddAndEqual(axEnc, ex, message.l);

    context.Mul(bxEnc, vx, encKey.bx, message.l);

    context.SampleGauss(ex, message.l);
    context.NTTAndEqual(ex, message.l);

    context.AddAndEqual(bxEnc, ex, message.l);
    context.AddAndEqual(bxEnc, message.mx, message.l);

    return Ciphertext(axEnc, bxEnc, context.N, message.slots, message.l, context.p);
}

/*
 * @brief 解密密文，恢复出明文对象。
 *
 * @param secretKey： 输入的私钥，用于解密操作。
 * @param cipher： 输入的密文对象，包含加密后的数据及元数据。
 *
 * @return Plaintext： 返回解密后的明文对象，包含恢复的多项式数据及元数据。
 *
 * 该函数通过给定的私钥 `secretKey` 对密文 `cipher` 进行解密，
 * 计算出明文多项式数据并构造 `Plaintext` 对象返回。
 *
 * 实现步骤：
 * 1. 使用密文的 `ax` 和私钥 `sx` 进行多项式乘法，结果存储到 `mx`。
 * 2. 将密文的 `bx` 加入到结果 `mx`，完成解密过程。
 * 3. 构造并返回包含解密数据的 `Plaintext` 对象。
 */
Plaintext Scheme::DecryptMsg(SecretKey &secretKey, Ciphertext &cipher)
{
    uint64_t *mx = new uint64_t[context.N * cipher.l]();
    context.Mul(mx, cipher.ax, secretKey.sx, cipher.l);
    context.AddAndEqual(mx, cipher.bx, cipher.l);

    return Plaintext(mx, context.N, cipher.slots, cipher.l);
}

/*
 * @brief 对输入的浮点数数组进行加密，生成密文对象。
 *
 * @param vals： 输入的浮点数数组，待加密的数据。
 * @param slots： 数据的槽位数量，每个槽位对应一个输入值。
 * @param l： 模数层级数量，表示加密所需的层级深度。
 *
 * @return Ciphertext： 返回加密后的密文对象。
 */
Ciphertext Scheme::Encrypt(double *vals, long slots, long l)
{
    Plaintext msg = Encode(vals, slots, l);
    return EncryptMsg(msg);
}

/*
 * @brief 对输入的复数数组进行加密，生成密文对象。
 *
 * @param vals： 输入的复数数组，待加密的数据。
 * @param slots： 数据的槽位数量，每个槽位对应一个输入值。
 * @param l： 模数层级数量，表示加密所需的层级深度。
 *
 * @return Ciphertext： 返回加密后的密文对象。
 */
Ciphertext Scheme::Encrypt(complex<double> *vals, long slots, long l)
{
    Plaintext msg = Encode(vals, slots, l);
    return EncryptMsg(msg);
}

/*
 * @brief 对输入的浮点数数组进行加密，使用给定的私钥生成密文对象。
 *
 * @param secretKey： 输入的私钥，用于加密操作。
 * @param vals： 输入的浮点数数组，待加密的数据。
 * @param slots： 数据的槽位数量，每个槽位对应一个输入值。
 * @param l： 模数层级数量，表示加密所需的层级深度。
 *
 * @return Ciphertext： 返回加密后的密文对象。
 */
Ciphertext Scheme::EncryptSk(SecretKey& secretKey, double* vals, long slots, long l)
{
    Plaintext msg = Encode(vals, slots, l);
    return EncryptMsg(secretKey, msg);
}

/*
 * @brief 对输入的复数数组进行加密，使用给定的私钥生成密文对象。
 *
 * @param secretKey： 输入的私钥，用于加密操作。
 * @param vals： 输入的复数数组，待加密的数据。
 * @param slots： 数据的槽位数量，每个槽位对应一个输入值。
 * @param l： 模数层级数量，表示加密所需的层级深度。
 *
 * @return Ciphertext： 返回加密后的密文对象。
 */
Ciphertext Scheme::EncryptSk(SecretKey& secretKey, complex<double>* vals, long slots, long l)
{
    Plaintext msg = Encode(vals, slots, l);
    return EncryptMsg(secretKey, msg);
}

Ciphertext Scheme::EncryptSingle(complex<double> val, long l)
{
    Plaintext msg = EncodeSingle(val, l);
    return EncryptMsg(msg);
}

/*
 * @brief 使用私钥解密密文，恢复出解密后的复数数组。
 *
 * @param secretKey： 输入的私钥，用于解密操作。
 * @param cipher： 输入的密文对象，包含加密后的数据及元数据。
 *
 * @return complex<double>*： 返回解密并解码后的复数数组。
 */
complex<double> *Scheme::Decrypt(SecretKey &secretKey, Ciphertext &cipher)
{
    Plaintext msg = DecryptMsg(secretKey, cipher);
    return Decode(msg, cipher.scale);
}

complex<double> Scheme::DecryptSingle(SecretKey &secretKey, Ciphertext &cipher)
{
    Plaintext msg = DecryptMsg(secretKey, cipher);
    return DecodeSingle(msg);
}

/*
 * @brief 对输入的密文执行取负操作，返回结果密文。
 *
 * @param cipher： 输入的密文对象，待取负的数据。
 *
 * @return Ciphertext： 返回取负后的密文对象。
 */
Ciphertext Scheme::Negate(Ciphertext &cipher)
{
    uint64_t *axres = new uint64_t[cipher.l << context.logN];
    uint64_t *bxres = new uint64_t[cipher.l << context.logN];

    context.Negate(axres, cipher.ax, cipher.l);
    context.Negate(bxres, cipher.bx, cipher.l);

    return Ciphertext(axres, bxres, context.N, cipher.slots, cipher.l, cipher.scale);
}

void Scheme::NegateAndEqual(Ciphertext &cipher)
{
    long shift = 0;
    for (long i = 0; i < cipher.l; i++) {
        context.QiNegateAndEqual(cipher.ax + shift, i);
        context.QiNegateAndEqual(cipher.bx + shift, i);
        shift += context.N;
    }
}

/*
 * @brief 对两个密文执行加法操作，返回加法结果密文。
 *
 * @param cipher1： 输入的第一个密文对象。
 * @param cipher2： 输入的第二个密文对象。
 *
 * @return Ciphertext： 返回加法结果的密文对象。
 */
Ciphertext Scheme::Add(Ciphertext &cipher1, Ciphertext &cipher2)
{
    uint64_t *axres = new uint64_t[cipher1.l << context.logN];
    uint64_t *bxres = new uint64_t[cipher1.l << context.logN];
    int minLevel = min(cipher1.l, cipher2.l);
    context.Add(axres, cipher1.ax, cipher2.ax, minLevel);
    context.Add(bxres, cipher1.bx, cipher2.bx, minLevel);

    return Ciphertext(axres, bxres, context.N, cipher1.slots, minLevel, cipher2.scale);
}

void Scheme::Add(Ciphertext &res, Ciphertext &cipher1, Ciphertext &cipher2)
{
    res.l = min(cipher1.l, cipher2.l);
    context.Add(res.ax, cipher1.ax, cipher2.ax, res.l);
    context.Add(res.bx, cipher1.bx, cipher2.bx, res.l);
}

void Scheme::AddAndEqual(Ciphertext &cipher1, Ciphertext &cipher2)
{
    cipher1.l = min(cipher1.l, cipher2.l);
    context.Add(cipher1.ax, cipher1.ax, cipher2.ax, cipher1.l);
    context.Add(cipher1.bx, cipher1.bx, cipher2.bx, cipher1.l);
}

/*
 * @brief 对两个密文执行减法操作，返回减法结果密文。
 *
 * @param cipher1： 输入的第一个密文对象（被减数）。
 * @param cipher2： 输入的第二个密文对象（减数）。
 *
 * @return Ciphertext： 返回减法结果的密文对象。
 *
 * @throws invalid_argument： 如果两个密文的层级数量 `l` 不相等，则抛出异常。
 */
Ciphertext Scheme::Sub(Ciphertext &cipher1, Ciphertext &cipher2)
{
    if (cipher1.l != cipher2.l) {
        throw invalid_argument("Ciphertexts are not comparable");
    }

    uint64_t *axres = new uint64_t[cipher1.l << context.logN];
    uint64_t *bxres = new uint64_t[cipher1.l << context.logN];

    context.Sub(axres, cipher1.ax, cipher2.ax, cipher1.l);
    context.Sub(bxres, cipher1.bx, cipher2.bx, cipher1.l);

    return Ciphertext(axres, bxres, context.N, cipher1.slots, cipher1.l, cipher1.scale);
}

void Scheme::SubAndEqual(Ciphertext &cipher1, Ciphertext &cipher2)
{
    if (cipher1.l != cipher2.l) {
        throw invalid_argument("Ciphertexts are not comparable");
    }

    context.SubAndEqual(cipher1.ax, cipher2.ax, cipher1.l);
    context.SubAndEqual(cipher1.bx, cipher2.bx, cipher1.l);
}

void Scheme::Sub2AndEqual(Ciphertext &cipher1, Ciphertext &cipher2)
{
    if (cipher1.l != cipher2.l) {
        throw invalid_argument("Ciphertexts are not comparable");
    }

    context.Sub2AndEqual(cipher1.ax, cipher2.ax, cipher1.l);
    context.Sub2AndEqual(cipher1.bx, cipher2.bx, cipher1.l);
}


void Scheme::ExternalProduct(int levelQ, 
    vector<uint64_t *> &aPolyTs, 
    vector<vector<vector<uint64_t>>>& axkey, 
    vector<vector<vector<uint64_t>>>& bxkey, 
    uint64_t* axres, uint64_t* bxres)
{
    int blockLen = ceil((levelQ + context.K) / (float(context.gamma)));
    int beta = ceil(levelQ / (float(context.K)));
    uint64_t tN;
    uint64_t tjN;
    #pragma omp parallel for
    for (int j = 0; j < blockLen; j++) {
        memset(polyTPools2Ax[j], 0, sizeof(uint64_t) * context.T * context.N);
        memset(polyTPools2Bx[j], 0, sizeof(uint64_t) * context.T * context.N);
    }
    for (int j = 0; j < blockLen; j++) {
        for (int t = 0; t < context.T; t++) {
            uint64x2_t tVec = vdupq_n_u64(context.tVec[t]);
            uint64x2_t twotVec = vdupq_n_u64(context.twotVec[t]);
            uint64x2_t trVec_high = vdupq_n_u64(context.trVecHigh[t]);
            uint64x2_t trVec_low = vdupq_n_u64(context.trVecLow[t]);
            tN = t * context.N;
            tjN = j * context.TN + tN;
            for (int i = 0; i < beta; i++) {
            #pragma omp parallel for
                for (int n = 0; n < context.N; n += 2) {
                    uint64x2_t aVec1 = vld1q_u64(&aPolyTs[i][tN + n]);
                    uint64x2_t keyVecAx = vld1q_u64(&axkey[i][j][tN + n]);
                    uint64x2_t keyVecBx = vld1q_u64(&bxkey[i][j][tN + n]);

                    uint64x2_t resultAx;
                    uint64x2_t resultBx;
                    MulModBarrettNeon(&resultAx, aVec1, keyVecAx, tVec, twotVec, trVec_high, trVec_low);
                    MulModBarrettNeon(&resultBx, aVec1, keyVecBx, tVec, twotVec, trVec_high, trVec_low);
                    {
                        uint64x2_t tmpValAx = vld1q_u64(&polyTPools2Ax[j][tN + n]);
                        uint64x2_t tmpValBx = vld1q_u64(&polyTPools2Bx[j][tN + n]);
                        uint64x2_t sumAx = vaddq_u64(tmpValAx, resultAx);
                        uint64x2_t sumBx = vaddq_u64(tmpValBx, resultBx);
                        uint64x2_t cmpAx = vcgtq_u64(sumAx, tVec);
                        uint64x2_t cmpBx = vcgtq_u64(sumBx, tVec);
                        tmpValAx = vsubq_u64(sumAx, vandq_u64(cmpAx, tVec));
                        tmpValBx = vsubq_u64(sumBx, vandq_u64(cmpBx, tVec));
                        vst1q_u64(&polyTPools2Ax[j][tN + n], tmpValAx);
                        vst1q_u64(&polyTPools2Bx[j][tN + n], tmpValBx);
                    }
                }
            }
        }
    }
    #pragma omp parallel for
    for (int i = 0; i < blockLen * context.T; i++) {
        uint64_t *ai = polyTPools2Ax[i / context.T] + ((i % context.T) << context.logN);
        uint64_t *bi = polyTPools2Bx[i / context.T] + ((i % context.T) << context.logN);
        context.TiINTTAndEqual(ai, i % context.T);
        context.TiINTTAndEqual(bi, i % context.T);
        context.TiAddAndEqual(ai, context.halfTPolyT + ((i % context.T) << context.logN), i % context.T);
        context.TiAddAndEqual(bi, context.halfTPolyT + ((i % context.T) << context.logN), i % context.T);
    }
    
    context.BaseConvertT2PQl(polyPQPoolAx, polyTPools2Ax, levelQ);
    context.BaseConvertT2PQl(polyPQPoolBx, polyTPools2Bx, levelQ);
    context.TSubAndEqual(polyPQPoolAx, context.halfTPolyPQ, levelQ, context.K);
    context.TSubAndEqual(polyPQPoolBx, context.halfTPolyPQ, levelQ, context.K);
    context.BaseConvertPQl2Ql(axres, polyPQPoolAx, levelQ);
    context.BaseConvertPQl2Ql(bxres, polyPQPoolBx, levelQ);
}



/*
 * @brief 执行密钥切换操作，将输入多项式结果重新映射到新的密钥下。
 *
 * @param levelQ： 当前的模数层级数量。
 * @param axaxKSW： 输入的多项式，表示需要进行密钥切换的中间结果。
 * @param rax： 预计算的密钥切换向量，用于 ax 的转换。
 * @param rbx： 预计算的密钥切换向量，用于 bx 的转换。
 * @param axmultKSW： 输出数组，存储密钥切换后的 ax 结果。
 * @param bxmultKSW： 输出数组，存储密钥切换后的 bx 结果。
 *
 * 实现步骤：
 * 1. 将输入多项式 `axaxKSW` 从模数集合 Ql 转换到模数集合 T。
 * 2. 对转换结果进行 NTT 变换。
 * 3. 计算与密钥切换向量 `rax` 和 `rbx` 的外积，将结果拷贝到 `axmultKSW` 和 `bxmultKSW`。
 */
void Scheme::KeySwitch(int levelQ, uint64_t *axaxKSW, vector<vector<vector<uint64_t>>>& rax,
                       vector<vector<vector<uint64_t>>>& rbx, uint64_t *axmultKSW, uint64_t *bxmultKSW)
{
    int beta = ceil(levelQ / float(context.K));
    context.BaseConvertQl2T(axaxKSW, levelQ, context.polyTPools);
#pragma omp parallel for
    for (int j = 0; j < beta * context.T; j++) {
        uint64_t *ai = context.polyTPools[j / context.T] + ((j % context.T) << context.logN);
        context.TiNTTAndEqual(ai, j % context.T);
    }
    ExternalProduct(levelQ, context.polyTPools, rax, rbx, axmultKSW, bxmultKSW);
    return;
}



/*
 * @brief 对两个密文对象进行乘法操作，返回乘积后的密文对象。
 *
 * @param res： 返回乘法结果的密文对象，包含乘法后的数据及元数据。
 * @param cipher1： 第一个输入的密文对象，包含加密后的数据及元数据。
 * @param cipher2： 第二个输入的密文对象，包含加密后的数据及元数据。
 *
 * 该函数对两个密文对象 `cipher1` 和 `cipher2` 进行乘法操作，计算出它们的乘积密文，并返回一个新的密文对象。
 */
void Scheme::Mult(Ciphertext& res, Ciphertext &cipher1, Ciphertext &cipher2)
{
    int minLevel = min(cipher1.l, cipher2.l);
    #pragma omp parallel for collapse(2)
    for (long n = 0; n < context.N; n++) {
        for (int i = 0; i < minLevel; i++) {
            int index = (i << context.logN) + n;
            axbx1[index] = cipher1.ax[index] + cipher1.bx[index];
            axbx2[index] = cipher2.ax[index] + cipher2.bx[index];
            if (axbx1[index] > context.qVec[i]) {
                axbx1[index] -= context.qVec[i];
            } 
            if (axbx2[index] > context.qVec[i]) {
                axbx2[index] -= context.qVec[i];
            }
            MulModBarrett(axbx1[index], axbx1[index], axbx2[index], context.qVec[i], context.twoqVec[i],
                        context.qrVecHigh[i], context.qrVecLow[i]);
            MulModBarrett(bxbx[index], cipher1.bx[index], cipher2.bx[index], context.qVec[i], context.twoqVec[i],
                        context.qrVecHigh[i], context.qrVecLow[i]);
            MulModBarrett(axax[index], cipher1.ax[index], cipher2.ax[index], context.qVec[i], context.twoqVec[i],
                        context.qrVecHigh[i], context.qrVecLow[i]);
        }
        
    }
    context.INTT(axaxi, axax, minLevel);
    KeySwitch(minLevel, axaxi, multKey.rax, multKey.rbx, res.ax, res.bx);
    context.NTTAndEqual(res.ax, minLevel);
    context.NTTAndEqual(res.bx, minLevel);
    #pragma omp parallel for collapse(2)
    for (long n = 0; n < context.N; n++) {
        for (int i = 0; i < minLevel; i++) {
            int index = (i << context.logN) + n;
            res.bx[index] = res.bx[index] + bxbx[index];
            if (res.bx[index] > context.qVec[i]) {
                res.bx[index] -= context.qVec[i];
            } 
            res.ax[index] = res.ax[index] + axbx1[index];
            res.ax[index] = (res.ax[index] + context.twoqVec[i] - bxbx[index] - axax[index]) % context.qVec[i];
        }
        
    }
    res.l = minLevel;
    res.scale = cipher1.scale * cipher2.scale;
}


/*
 * @brief 对两个密文对象进行乘法并更新第一个密文对象。
 *
 * @param cipher1： 第一个输入的密文对象，经过更新后存储乘法结果。
 * @param cipher2： 第二个输入的密文对象，与第一个密文对象进行乘法。
 *
 * 该函数执行两个密文对象的乘法操作，直接修改 `cipher1`，并进行必要的密钥切换、数值变换以及其他操作。
 */
void Scheme::MultAndEqual(Ciphertext &cipher1, Ciphertext &cipher2)
{
    cipher1.l = min(cipher1.l, cipher2.l);
    #pragma omp parallel for collapse(2)
    for (long n = 0; n < context.N; n++) {
        for (int i = 0; i < cipher1.l; i++) {
            int index = (i << context.logN) + n;
            axbx1[index] = cipher1.ax[index] + cipher1.bx[index];
            axbx2[index] = cipher2.ax[index] + cipher2.bx[index];
            if (axbx1[index] > context.qVec[i]) {
                axbx1[index] -= context.qVec[i];
            } 
            if (axbx2[index] > context.qVec[i]) {
                axbx2[index] -= context.qVec[i];
            }

            MulModBarrett(axbx1[index], axbx1[index], axbx2[index], context.qVec[i], context.twoqVec[i],
                        context.qrVecHigh[i], context.qrVecLow[i]);
            MulModBarrett(bxbx[index], cipher1.bx[index], cipher2.bx[index], context.qVec[i], context.twoqVec[i],
                        context.qrVecHigh[i], context.qrVecLow[i]);
            MulModBarrett(axax[index], cipher1.ax[index], cipher2.ax[index], context.qVec[i], context.twoqVec[i],
                        context.qrVecHigh[i], context.qrVecLow[i]);
        }
        
    }
    context.INTT(axaxi, axax, cipher1.l);
    KeySwitch(cipher1.l, axaxi, multKey.rax, multKey.rbx, cipher1.ax, cipher1.bx);
    context.NTTAndEqual(cipher1.ax, cipher1.l);
    context.NTTAndEqual(cipher1.bx, cipher1.l);
    #pragma omp parallel for collapse(2)
    for (long n = 0; n < context.N; n++) {
        for (int i = 0; i < cipher1.l; i++) {
            int index = (i << context.logN) + n;
            cipher1.bx[index] = cipher1.bx[index] + bxbx[index];
            if (cipher1.bx[index] > context.qVec[i]) {
                cipher1.bx[index] -= context.qVec[i];
            } 
            cipher1.ax[index] = cipher1.ax[index] + axbx1[index];
            cipher1.ax[index] = (cipher1.ax[index] + context.twoqVec[i] - bxbx[index] - axax[index]) % context.qVec[i];
        }
        
    }
    cipher1.scale = cipher1.scale * cipher2.scale;
}

/*
 * @brief 对密文对象执行平方操作，返回平方后的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 *
 * @param Ciphertext： 返回平方操作后的密文对象，包含平方后的数据及元数据。
 *
 * 该函数对密文对象 `cipher` 执行平方操作，计算密文的平方并返回结果。
 */
Ciphertext Scheme::Square(Ciphertext &cipher)
{
    uint64_t *axSquare = new uint64_t[(cipher.l + context.K) << context.logN]();
    uint64_t *bxSquare = new uint64_t[(cipher.l + context.K) << context.logN]();

    context.Add(axbx, cipher.ax, cipher.bx, cipher.l);

    context.SquareAndEqual(axbx, cipher.l);
    context.Square(bxbx, cipher.bx, cipher.l);
    context.Square(axax, cipher.ax, cipher.l);

    context.INTTAndEqual(axax, cipher.l);
    KeySwitch(cipher.l, axax, multKey.rax, multKey.rbx, axSquare, bxSquare);
    context.NTTAndEqual(axSquare, cipher.l);
    context.NTTAndEqual(bxSquare, cipher.l);
    context.NTTAndEqual(axax, cipher.l);

    context.AddAndEqual(axSquare, axbx, cipher.l);
    context.SubAndEqual(axSquare, bxbx, cipher.l);
    context.SubAndEqual(axSquare, axax, cipher.l);
    context.AddAndEqual(bxSquare, bxbx, cipher.l);

    return Ciphertext(axSquare, bxSquare, context.N, cipher.slots, cipher.l, cipher.scale * cipher.scale);
}

void Scheme::Square(Ciphertext &res, Ciphertext &cipher)
{
    context.Add(axbx, cipher.ax, cipher.bx, cipher.l);

    context.SquareAndEqual(axbx, cipher.l);
    context.Square(bxbx, cipher.bx, cipher.l);
    context.Square(axax, cipher.ax, cipher.l);

    context.INTT(axaxi, axax, cipher.l);
    KeySwitch(cipher.l, axaxi, multKey.rax, multKey.rbx, res.ax, res.bx);
    context.NTTAndEqual(res.ax, cipher.l);
    context.NTTAndEqual(res.bx, cipher.l);
    context.AddAndEqual(res.ax, axbx, cipher.l);
    context.SubAndEqual(res.ax, bxbx, cipher.l);
    context.SubAndEqual(res.ax, axax, cipher.l);
    context.AddAndEqual(res.bx, bxbx, cipher.l);
    res.scale =  cipher.scale * cipher.scale;
}

/*
 * @brief 对密文对象执行平方操作并直接修改该密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据，平方后的结果将直接修改该对象。
 *
 * 该函数通过对密文对象 `cipher` 进行平方操作，直接修改原密文的 `ax` 和 `bx`，并更新相应的元数据。
 */
void Scheme::SquareAndEqual(Ciphertext &cipher)
{
    context.Add(axbx, cipher.ax, cipher.bx, cipher.l);
    context.SquareAndEqual(axbx, cipher.l);
    context.Square(bxbx, cipher.bx, cipher.l);
    context.Square(axax, cipher.ax, cipher.l);
    context.INTT(axaxi, axax, cipher.l);
    KeySwitch(cipher.l, axaxi, multKey.rax, multKey.rbx, cipher.ax, cipher.bx);
    context.NTTAndEqual(cipher.ax, cipher.l);
    context.NTTAndEqual(cipher.bx, cipher.l);
    context.AddAndEqual(cipher.ax, axbx, cipher.l);
    context.SubAndEqual(cipher.ax, bxbx, cipher.l);
    context.SubAndEqual(cipher.ax, axax, cipher.l);
    context.AddAndEqual(cipher.bx, bxbx, cipher.l);
    cipher.scale *= cipher.scale;
}

/*
 * @brief 对密文对象进行乘法操作，乘以虚数单位 `i`。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 *
 * @return Ciphertext： 返回乘以虚数单位 `i` 后的密文对象。
 *
 * 该函数对密文对象 `cipher` 执行乘以虚数单位 `i`（即 `i` 乘法）操作，计算并返回新的密文对象。
 * 具体来说，它对密文的每个元素进行加权乘法，其中虚数单位 `i` 的影响通过预定义的根幂和模数来实现。
 */
Ciphertext Scheme::MultByi(Ciphertext &cipher)
{
    uint64_t imag;
    uint64_t *axMulI = new uint64_t[cipher.l << context.logN]();
    uint64_t *bxMulI = new uint64_t[cipher.l << context.logN]();
    for (int i = 0; i < cipher.l; i++) {
        imag = context.qRootPows[i][1];
        for (int j = 0; j < context.N / 2; j++) {
            MulModBarrett(axMulI[i * context.N + j], cipher.ax[i * context.N + j], imag, context.qVec[i],
                          context.twoqVec[i], context.qrVecHigh[i], context.qrVecLow[i]);
            MulModBarrett(bxMulI[i * context.N + j], cipher.bx[i * context.N + j], imag, context.qVec[i],
                          context.twoqVec[i], context.qrVecHigh[i], context.qrVecLow[i]);
        }
        imag = context.qVec[i] - context.qRootPows[i][1];
        for (int j = context.N / 2; j < context.N; j++) {
            MulModBarrett(axMulI[i * context.N + j], cipher.ax[i * context.N + j], imag, context.qVec[i],
                          context.twoqVec[i], context.qrVecHigh[i], context.qrVecLow[i]);
            MulModBarrett(bxMulI[i * context.N + j], cipher.bx[i * context.N + j], imag, context.qVec[i],
                          context.twoqVec[i], context.qrVecHigh[i], context.qrVecLow[i]);
        }
    }
    return Ciphertext(axMulI, bxMulI, context.N, cipher.slots, cipher.l, context.p);
}

/*
 * @brief 向密文对象添加常数值，返回添加常数后的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param cnst： 要添加的常数值，可以是正数或负数。
 *
 * @return Ciphertext： 返回添加常数值后的密文对象。
 *
 * 该函数对密文对象 `cipher` 添加常数值 `cnst`，并返回新的密文对象。
 * 具体来说，它根据常数 `cnst` 的符号，分别对密文的虚部 `bx` 执行加法或减法操作，保持实部 `ax` 不变。
 */
Ciphertext Scheme::AddConst(Ciphertext &cipher, double cnst)
{
    uint64_t tmpr = abs(cnst) * context.p;
    uint64_t *axAddCnst = new uint64_t[cipher.l << context.logN]();
    uint64_t *bxAddCnst = new uint64_t[cipher.l << context.logN]();
    copy(cipher.ax, cipher.ax + (cipher.l << context.logN), axAddCnst);

    if (cnst >= 0) {
        context.AddConst(bxAddCnst, cipher.bx, tmpr, cipher.l);
    } else {
        context.SubConst(bxAddCnst, cipher.bx, tmpr, cipher.l);
    }
    return Ciphertext(axAddCnst, bxAddCnst, context.N, cipher.slots, cipher.l, cipher.scale);
}

/*
 * @brief 向密文对象添加常数值，直接修改原密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param cnst： 要添加的常数值，可以是正数或负数。
 */
void Scheme::AddConstAndEqual(Ciphertext &cipher, double cnst)
{
    uint64_t tmpr = abs(cnst) * cipher.scale;
    if (cnst >= 0) {
        context.AddConstAndEqual(cipher.bx, tmpr, cipher.l);
    } else {
        context.SubConstAndEqual(cipher.bx, tmpr, cipher.l);
    }
}

/*
 * @brief 向密文对象乘以常数，返回乘以常数后的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param cnst： 要乘以的常数值。
 *
 * @return Ciphertext： 返回乘以常数后的密文对象。
 *
 */
Ciphertext Scheme::MultByConst(Ciphertext &cipher, double cnst)
{
    cnstvec[0].real(cnst);
    cnstvec[0].imag(0);
    return MultByConstVec(cipher, cnstvec, 1);
}

/*
 * @brief 对密文对象乘以一个复数常数，返回乘以常数后的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param cnst： 复数常数，实部和虚部分别表示为 `cnst.real()` 和 `cnst.imag()`。
 *
 * @return Ciphertext： 返回乘以复数常数后的密文对象。
 */
Ciphertext Scheme::MultByConst(Ciphertext &cipher, complex<double> cnst)
{
    for (long i = 0; i < cipher.slots; i++) {
        cnstvec[i].real(cnst.real());
        cnstvec[i].imag(cnst.imag());
    }
    return MultByConstVec(cipher, cnstvec, cipher.slots);
}

/*
 * @brief 对密文对象与一个常数向量进行逐项乘法，返回乘法后的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param cnstVec： 常数向量。
 * @param slots： 向量的长度。
 *
 * @return Ciphertext： 返回乘以常数向量后的密文对象。
 *
 * 该函数将常数向量 `cnstVec` 与密文对象 `cipher` 逐项相乘，返回新的密文对象。
 * 通过调用 `Encode` 编码常数向量，并进行逐项乘法。
 */
Ciphertext Scheme::MultByConstVec(Ciphertext &cipher, double *cnstVec, long slots)
{
    uint64_t *axMulCnst = new uint64_t[cipher.l << context.logN]();
    uint64_t *bxMulCnst = new uint64_t[cipher.l << context.logN]();
    uint64_t *mx = new uint64_t[cipher.l << context.logN]();

    context.Encode(mx, cnstVec, slots, cipher.l);
    context.Mul(axMulCnst, cipher.ax, mx, cipher.l);
    context.Mul(bxMulCnst, cipher.bx, mx, cipher.l);
    delete[] mx;
    return Ciphertext(axMulCnst, bxMulCnst, context.N, cipher.slots, cipher.l, cipher.scale * cipher.scale);
}

/*
 * @brief 对密文对象与一个复数常数向量进行逐项乘法，返回乘法后的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param cnstVec： 复数常数向量。
 * @param slots： 向量的长度。
 *
 * @return Ciphertext： 返回乘以复数常数向量后的密文对象。
 *
 * 该函数将复数常数向量 `cnstVec` 与密文对象 `cipher` 逐项相乘，返回新的密文对象。
 * 通过调用 `Encode` 编码复数常数向量，并进行逐项乘法。
 */
Ciphertext Scheme::MultByConstVec(Ciphertext &cipher, complex<double> *cnstVec, long slots)
{
    uint64_t *axMulCnst = new uint64_t[cipher.l << context.logN]();
    uint64_t *bxMulCnst = new uint64_t[cipher.l << context.logN]();
    memset(mx_1, 0, sizeof(uint64_t) * cipher.l * context.N);

    context.Encode(mx_1, cnstVec, slots, cipher.l);
    context.Mul(axMulCnst, cipher.ax, mx_1, cipher.l);
    context.Mul(bxMulCnst, cipher.bx, mx_1, cipher.l);
    return Ciphertext(axMulCnst, bxMulCnst, context.N, cipher.slots, cipher.l, cipher.scale * cipher.scale);
}

/*
 * @brief 将密文对象乘以一个常数，并直接更新原始密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param cnst： 乘以的常数。
 *
 * @return void： 该函数修改输入的密文对象 `cipher`。
 *
 * 该函数将常数 `cnst` 乘以密文对象 `cipher`，并直接修改原始密文对象。
 */
void Scheme::MultByConstAndEqual(Ciphertext &cipher, double cnst)
{
    uint64_t tmpr = abs(cnst) * context.p;
    context.MulConstAndEqual(cipher.ax, tmpr, cipher.l);
    context.MulConstAndEqual(cipher.bx, tmpr, cipher.l);

    if (cnst < 0) {
        context.NegateAndEqual(cipher.ax, cipher.l);
        context.NegateAndEqual(cipher.bx, cipher.l);
    }
    cipher.scale *= cipher.scale;
}


/*
 * @brief 直接修改原始密文对象，通过缩放操作调整尺度。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param dl： 需要缩放的层数。
 *
 * @return void： 该函数直接修改密文对象 `cipher` 的尺度。
 *
 * 该函数通过调整 `cipher` 的尺度，直接修改原始密文对象。
 *
 * 实现步骤：
 * 1. 对密文对象 `cipher` 的 `ax` 和 `bx` 调用 `ReScaleAndEqual` 函数进行缩放。
 * 2. 调整密文对象 `cipher` 的尺度 `scale`。
 */
void Scheme::ReScaleByAndEqual(Ciphertext &cipher, long dl)
{
    if(cipher.l <= 1) {
        throw std::runtime_error("The number of ciphertext layers is less than or equal to 1.");
    }
    for (long i = 0; i < dl; i++) {
        context.ReScaleAndEqual(cipher.ax, cipher.l);
        context.ReScaleAndEqual(cipher.bx, cipher.l);
        cipher.scale = cipher.scale / context.qVec[cipher.l - 1];
        cipher.l -= 1;
    }
}


/*
 * @brief 将密文对象的尺度直接缩放至 `l`。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param l： 目标尺度值。
 *
 * @return void： 该函数直接修改密文对象 `cipher` 的尺度。
 *
 * 该函数通过计算需要缩放的层数 `dl`，然后调用 `ReScaleByAndEqual` 函数，直接修改原始密文对象。
 */
void Scheme::ReScaleToAndEqual(Ciphertext &cipher, long l)
{
    long dl = cipher.l - l;
    ReScaleByAndEqual(cipher, dl);
}

/*
 * @brief 将密文对象按层数 `dl` 下调，并返回新的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param dl： 下调的层数。
 *
 * @return Ciphertext： 返回下调后的密文对象。
 *
 * 该函数通过调用 `ModDown` 对密文对象 `cipher` 进行下调，并返回新的密文对象。
 */
Ciphertext Scheme::ModDownBy(Ciphertext &cipher, long dl)
{
    uint64_t *axModDown = context.ModDown(cipher.ax, cipher.l, dl);
    uint64_t *bxModDown = context.ModDown(cipher.bx, cipher.l, dl);
    return Ciphertext(axModDown, bxModDown, context.N, cipher.slots, cipher.l - dl, cipher.scale);
}

/*
 * @brief 将密文对象按层数 `dl` 下调，并更新原密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param dl： 下调的层数。
 *
 * 该函数通过调用 `ModDown` 对密文对象 `cipher` 进行下调，并更新原密文对象的层数。
 */
void Scheme::ModDownByAndEqual(Ciphertext &cipher, long dl)
{
    context.ModDownAndEqual(cipher.ax, cipher.l, dl);
    context.ModDownAndEqual(cipher.bx, cipher.l, dl);
    cipher.l -= dl;
}

/*
 * @brief 将密文对象的层数下调到指定的层数 `l`，并返回新的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param l： 目标层数。
 *
 * @return Ciphertext： 返回下调到指定层数 `l` 的密文对象。
 *
 * 该函数通过计算原密文与目标层数 `l` 之间的差值，并调用 `ModDownBy` 函数来生成下调后的密文对象。
 */
Ciphertext Scheme::ModDownTo(Ciphertext &cipher, long l)
{
    long dl = cipher.l - l;
    return ModDownBy(cipher, dl);
}

/*
 * @brief ： 将密文对象的层数下调到指定的层数 `l`，并更新原密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param l： 目标层数。
 *
 * 该函数通过计算原密文与目标层数 `l` 之间的差值，并调用 `ModDownByAndEqual` 函数来更新原密文对象。
 */
void Scheme::ModDownToAndEqual(Ciphertext &cipher, long l)
{
    long dl = cipher.l - l;
    ModDownByAndEqual(cipher, dl);
}

/*
 * @brief 执行快速的左旋转操作，将密文对象按 `rotSlots` 旋转，并返回新的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param rotSlots： 旋转的槽数。
 *
 * @return Ciphertext： 返回旋转后的密文对象。
 *
 * 该函数通过先进行逆NTT变换、然后进行密钥交换、最后进行NTT变换来完成旋转，并返回旋转后的密文对象。
 */
Ciphertext Scheme::LeftRotateFast(Ciphertext &cipher, long rotSlots)
{
    uint64_t *axRotate = new uint64_t[cipher.l << context.logN]();
    uint64_t *bxRotate = new uint64_t[cipher.l << context.logN]();

    uint64_t galElInv = context.FastPow(5, rotSlots, context.M);

    context.INTT(ax, cipher.ax, cipher.l);
    SwitchKey swtichkey = leftRotKeyMap.at(rotSlots);
    KeySwitch(cipher.l, ax, swtichkey.rax, swtichkey.rbx, axmult, bxmult);
    context.NTTAndEqual(axmult, cipher.l);
    context.NTTAndEqual(bxmult, cipher.l);

    context.AddAndEqual(bxmult, cipher.bx, cipher.l);
    context.PermuteNTTIndex(galElInv, ntt_index);
    context.PermuteNTTWithIndex(bxRotate, bxmult, ntt_index, cipher.l);
    context.PermuteNTTWithIndex(axRotate, axmult, ntt_index, cipher.l);
    return Ciphertext(axRotate, bxRotate, context.N, cipher.slots, cipher.l, cipher.scale);
}

/*
 * @brief 执行快速的左旋转操作，将密文对象按 `rotSlots` 旋转，并更新原密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param rotSlots： 旋转的槽数。
 *
 * 该函数通过先进行逆NTT变换、然后进行密钥交换、最后进行NTT变换来完成旋转，并更新原密文对象。
 */
void Scheme::LeftRotateAndEqualFast(Ciphertext &cipher, long rotSlots)
{
    TimeUtils time;
    uint64_t galElInv = context.FastPow(5, rotSlots, context.M);
    context.INTT(ax, cipher.ax, cipher.l);
    KeySwitch(cipher.l, ax, rotateKey.rax, rotateKey.rbx, axmult, bxmult);
    context.NTTAndEqual(axmult, cipher.l);
    context.NTTAndEqual(bxmult, cipher.l);
    context.AddAndEqual(bxmult, cipher.bx, cipher.l);
    context.PermuteNTTIndex(galElInv, ntt_index);
    context.PermuteNTTWithIndex(cipher.bx, bxmult, ntt_index, cipher.l);
    context.PermuteNTTWithIndex(cipher.ax, axmult, ntt_index, cipher.l);
}

/*
 * @brief 执行按2的幂次旋转，左旋转密文对象并返回新的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param logRotSlots： 旋转槽数的对数（log2值）。
 *
 * @return Ciphertext： 返回旋转后的密文对象。
 *
 * 该函数通过计算旋转的槽数 `rotSlots` 并调用 `LeftRotateFast` 来实现旋转。
 */
Ciphertext Scheme::LeftRotateByPo2(Ciphertext &cipher, long logRotSlots)
{
    long rotSlots = (1 << logRotSlots);
    return LeftRotateFast(cipher, rotSlots);
}

/*
 * @brief 执行按2的幂次旋转，左旋转密文对象并更新原密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param logRotSlots： 旋转槽数的对数（log2值）。
 *
 * 该函数通过计算旋转的槽数 `rotSlots` 并调用 `LeftRotateAndEqualFast` 来实现旋转。
 */
void Scheme::LeftRotateByPo2AndEqual(Ciphertext &cipher, long logRotSlots)
{
    long rotSlots = (1 << logRotSlots);
    LeftRotateAndEqualFast(cipher, rotSlots);
}

/*
 * @brief 执行按2的幂次右旋转，右旋转密文对象并返回新的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param logRotSlots： 旋转槽数的对数（log2值）。
 *
 * @return Ciphertext： 返回旋转后的密文对象。
 *
 * 该函数通过计算右旋转的槽数 `rotSlots` 并调用 `LeftRotateFast` 来实现右旋转。
 */
Ciphertext Scheme::RightRotateByPo2(Ciphertext &cipher, long logRotSlots)
{
    long rotSlots = context.Nh - (1 << logRotSlots);
    return LeftRotateFast(cipher, rotSlots);
}

/*
 * @brief 执行按2的幂次右旋转操作，更新密文对象并返回新的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param logRotSlots： 旋转槽数的对数（log2值）。
 *
 * @return Ciphertext： 返回右旋转后的密文对象。
 *
 * 该函数通过计算旋转槽数 `rotSlots` 并调用 `LeftRotateAndEqualFast` 来实现右旋转。
 */
void Scheme::RightRotateByPo2AndEqual(Ciphertext &cipher, long logRotSlots)
{
    long rotSlots = context.Nh - (1 << logRotSlots);
    LeftRotateAndEqualFast(cipher, rotSlots);
}

/*
 * @brief 执行按指定槽数 `rotSlots` 左旋转操作，返回新的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param rotSlots： 旋转的槽数。
 *
 * @return Ciphertext： 返回旋转后的密文对象。
 *
 * 该函数根据指定的旋转槽数调用 `LeftRotateAndEqual` 执行旋转，并返回新的密文对象。
 */
Ciphertext Scheme::LeftRotate(Ciphertext &cipher, long rotSlots)
{
    Ciphertext res = cipher;
    LeftRotateAndEqual(res, rotSlots);
    return res;
}

/*
 * @brief 按指定槽数 `rotSlots` 左旋转密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param rotSlots： 旋转的槽数。
 *
 * 该函数通过按位对 `rotSlots` 进行处理，将旋转过程分解为多个按2的幂次旋转，并执行这些旋转。
 */
void Scheme::LeftRotateAndEqual(Ciphertext &cipher, long rotSlots)
{
    LeftRotateAndEqualFast(cipher, rotSlots);
}

/*
 * @brief 执行按指定槽数 `rotSlots` 右旋转操作，返回新的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param rotSlots： 旋转的槽数。
 *
 * @return Ciphertext： 返回旋转后的密文对象。
 *
 * 该函数通过调用 `RightRotateAndEqual` 来实现右旋转操作并返回新密文对象。
 */
Ciphertext Scheme::RightRotate(Ciphertext &cipher, long rotSlots)
{
    Ciphertext res = cipher;
    RightRotateAndEqual(res, rotSlots);
    return res;
}

/*
 * @brief 按指定槽数 `rotSlots` 右旋转密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param rotSlots： 旋转的槽数。
 *
 * 该函数通过按位对 `rotSlots` 进行处理，将旋转过程分解为多个按2的幂次旋转，并执行这些旋转。
 */
void Scheme::RightRotateAndEqual(Ciphertext &cipher, long rotSlots)
{
    LeftRotateAndEqualFast(cipher, context.Nh - rotSlots);
}

/*
 * @brief 对密文对象进行共轭操作，并返回新的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 *
 * @return Ciphertext： 返回共轭后的密文对象。
 */


// Ciphertext Scheme::Conjugate(Ciphertext &cipher)
// {
//     context.Conjugate(bxbx, cipher.bx, cipher.l);
//     context.Conjugate(axax, cipher.ax, cipher.l);

//     context.INTT(axax, axax, cipher.l);
//     // SwitchKey switchKey = switchKeyMap.at(CONJUGATION);
//     KeySwitch(cipher.l, axax, conjKey.rax, conjKey.rbx, axmult, bxmult);
//     context.NTTAndEqual(axmult, cipher.l);
//     context.NTTAndEqual(bxmult, cipher.l);
//     context.AddAndEqual(bxmult, bxbx, cipher.l);

//     return Ciphertext(axmult, bxmult, context.N, cipher.slots, cipher.l, cipher.scale);
// }

void Scheme::Conjugate(Ciphertext &res, Ciphertext &cipher)
{
    context.Conjugate(bxbx, cipher.bx, cipher.l);
    context.Conjugate(axax, cipher.ax, cipher.l);

    context.INTT(axax, axax, cipher.l);
    // SwitchKey switchKey = switchKeyMap.at(CONJUGATION);
    KeySwitch(cipher.l, axax, conjKey.rax, conjKey.rbx, res.ax, res.bx);
    context.NTTAndEqual(res.ax, cipher.l);
    context.NTTAndEqual(res.bx, cipher.l);
    context.AddAndEqual(res.bx, bxbx, cipher.l);
}

/*
 * @brief 对密文对象执行共轭操作，并更新原密文对象。
 *
 * @param cipher： 输入的密文对象。
 */
void Scheme::ConjugateAndEqual(Ciphertext &cipher)
{
    context.Conjugate(bxbx, cipher.bx, cipher.l);
    context.Conjugate(axax, cipher.ax, cipher.l);

    context.INTT(axax, axax, cipher.l);
    // SwitchKey switchKey = switchKeyMap.at(CONJUGATION);
    KeySwitch(cipher.l, axax, conjKey.rax, conjKey.rbx, cipher.ax, cipher.bx);
    context.NTTAndEqual(cipher.ax, cipher.l);
    context.NTTAndEqual(cipher.bx, cipher.l);
    context.AddAndEqual(cipher.bx, bxbx, cipher.l);
}

/*
 * @brief 将密文对象下调到最小层数，并更新密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 *
 * 该函数将密文对象的层数下调至1，并更新密文对象的层数。
 */
void Scheme::ModDownToQ0AndEqual(Ciphertext &cipher)
{
    if (cipher.l == 1) {
        return;
    } else {
        context.ModDownAndEqual(cipher.ax, cipher.l, cipher.l - 1);
        context.ModDownAndEqual(cipher.bx, cipher.l, cipher.l - 1);
        cipher.l = 1;
    }
}

/*
 * @brief 将密文对象的层数上调到指定的 q，并更新密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 */
void Scheme::ModUpToQAndEqual(Ciphertext &cipher)
{
    ModDownToQ0AndEqual(cipher);

    long L = context.L;
    long N = context.N;
    uint64_t *a = cipher.ax;
    uint64_t *b = cipher.bx;

    uint64_t *ra = new uint64_t[(context.L << context.logN)];
    uint64_t *rb = new uint64_t[(context.L << context.logN)];

    copy(a, a + N, ra);
    copy(b, b + N, rb);

    context.INTTAndEqual(ra, 1);
    context.INTTAndEqual(rb, 1);

    for (int i = 0; i < N; i++) {
        int64_t q0 = context.qVec[0];
        int64_t central_ra = ra[i] % q0;
        central_ra = (central_ra >= q0 / 2) ? central_ra - q0 : central_ra;
        int64_t central_rb = rb[i] % q0;
        central_rb = (central_rb >= q0 / 2) ? central_rb - q0 : central_rb;
        for (int j = 1; j < L; j++) {
            int64_t qi = context.qVec[j];
            ra[i + j * N] = central_ra < 0 ? central_ra + 2 * qi : central_ra;
            rb[i + j * N] = central_rb < 0 ? central_rb + 2 * qi : central_rb;
            if (ra[i + j * N] > qi)
                ra[i + j * N] -= qi;
            if (rb[i + j * N] > qi)
                rb[i + j * N] -= qi;
        }
    }


    context.NTTAndEqual(ra, context.L);
    context.NTTAndEqual(rb, context.L);
    delete[] cipher.ax;
    delete[] cipher.bx;
    cipher.ax = ra;
    cipher.bx = rb;
    cipher.l = L;
}

/*
 * @brief 将密文对象的层数上调到指定的 q，并返回新的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 *
 */
Ciphertext Scheme::ModUpToQ(Ciphertext &cipher)
{
    ModDownToQ0AndEqual(cipher);

    long L = context.L;
    long N = context.N;
    uint64_t *a = cipher.ax;
    uint64_t *b = cipher.bx;


    uint64_t *ra = new uint64_t[(context.L << context.logN)];
    uint64_t *rb = new uint64_t[(context.L << context.logN)];

    copy(a, a + N, ra);
    copy(b, b + N, rb);

    context.INTTAndEqual(ra, 1);
    context.INTTAndEqual(rb, 1);
    for (int i = 0; i < N; i++) {
        int64_t q0 = context.qVec[0];
        int64_t central_ra = ra[i] % q0;
        central_ra = (central_ra >= q0 / 2) ? central_ra - q0 : central_ra;
        int64_t central_rb = rb[i] % q0;
        central_rb = (central_rb >= q0 / 2) ? central_rb - q0 : central_rb;
        for (int j = 1; j < L; j++) {
            int64_t qi = context.qVec[j];
            ra[i + j * N] = central_ra < 0 ? central_ra + 2 * qi : central_ra;
            rb[i + j * N] = central_rb < 0 ? central_rb + 2 * qi : central_rb;
            if (ra[i + j * N] > qi)
                ra[i + j * N] -= qi;
            if (rb[i + j * N] > qi)
                rb[i + j * N] -= qi;
        }
    }
    context.NTTAndEqual(ra, context.L);
    context.NTTAndEqual(rb, context.L);
    return Ciphertext(ra, rb, context.N, cipher.slots, context.L, cipher.scale);
}

// if slot * 2 = N, SubSum can be skipped
void Scheme::SubSum(Ciphertext &cipher)
{
    Ciphertext res = cipher;
    // TODO implement method
}

/*
 * @brief 执行密文和明文的乘法操作，返回新的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param plaintext： 输入的明文对象，包含加密所需的乘数。
 */
Ciphertext Scheme::MultPt(Ciphertext &cipher, Plaintext &plaintext)
{
    uint64_t *axMulPt = new uint64_t[cipher.l << context.logN]();
    uint64_t *bxMulPt = new uint64_t[cipher.l << context.logN]();
    context.Mul(axMulPt, cipher.ax, plaintext.mx, cipher.l);
    context.Mul(bxMulPt, cipher.bx, plaintext.mx, cipher.l);
    return Ciphertext(axMulPt, bxMulPt, context.N, cipher.slots, cipher.l, cipher.scale * context.p);
}

void Scheme::MultPt(Ciphertext &res, Ciphertext &cipher, Plaintext &plaintext)
{
    context.Mul(res.ax, cipher.ax, plaintext.mx, cipher.l);
    context.Mul(res.bx, cipher.bx, plaintext.mx, cipher.l);

    res.scale = cipher.scale * context.p;
}

/*
 * @brief 执行密文和明文的加法操作，返回新的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 * @param plaintext： 输入的明文对象，包含加密所需的加数。
 */
void Scheme::AddPt(Ciphertext &res, Ciphertext &cipher, Plaintext &plaintext)
{
    context.Add(res.bx, cipher.bx, plaintext.mx, cipher.l);
    std::copy(cipher.ax, cipher.ax + (cipher.l << context.logN), res.ax);
}

void Scheme::AddPt(Ciphertext &cipher, Plaintext &plaintext)
{
    context.Add(cipher.bx, cipher.bx, plaintext.mx, cipher.l);
}

/*
 * @brief 将密文对象除以 i，返回新的密文对象。
 *
 * @param cipher： 输入的密文对象，包含加密后的数据。
 */
Ciphertext Scheme::DivByi(Ciphertext &cipher)
{
    uint64_t imag;
    uint64_t *axDivI = new uint64_t[cipher.l << context.logN]();
    uint64_t *bxDivI = new uint64_t[cipher.l << context.logN]();
    for (int i = 0; i < cipher.l; i++) {
        imag = context.qVec[i] - context.qRootPows[i][1];

        for (int j = 0; j < context.N / 2; j++) {
            MulModBarrett(axDivI[i * context.N + j], cipher.ax[i * context.N + j], imag, context.qVec[i],
                          context.twoqVec[i], context.qrVecHigh[i], context.qrVecLow[i]);
            MulModBarrett(bxDivI[i * context.N + j], cipher.bx[i * context.N + j], imag, context.qVec[i],
                          context.twoqVec[i], context.qrVecHigh[i], context.qrVecLow[i]);
        }

        imag = context.qRootPows[i][1];
        for (int j = context.N / 2; j < context.N; j++) {
            MulModBarrett(axDivI[i * context.N + j], cipher.ax[i * context.N + j], imag, context.qVec[i],
                          context.twoqVec[i], context.qrVecHigh[i], context.qrVecLow[i]);
            MulModBarrett(bxDivI[i * context.N + j], cipher.bx[i * context.N + j], imag, context.qVec[i],
                          context.twoqVec[i], context.qrVecHigh[i], context.qrVecLow[i]);
        }
    }
    return Ciphertext(axDivI, bxDivI, context.N, cipher.slots, cipher.l, context.p);
}